﻿//copyright © kaveh.shahbazian@gmail.com 2013 (see license.txt)
open System
open System.Collections.Generic
open System.Linq
open System.Text
open System.Windows
open System.Windows.Controls
open System.Windows.Data
open System.Windows.Documents
open System.Windows.Input
open System.Windows.Media
open System.Windows.Media.Imaging
open System.Windows.Navigation
open System.Windows.Shapes

open System.Windows.Threading
open System.Threading
open System.Diagnostics
open System.IO

type t_str = System.String

let FsiPath = System.Configuration.ConfigurationManager.AppSettings.["FsiPath"]

let _app = Application()
let _window = Application.LoadComponent(new Uri("/Funtastic;component/MainWindow.xaml", UriKind.Relative)) :?> Window

let _4spaces = new t_str(' ', 4)

let mutable _fsi : Process = null
let mutable _previousKey : Key option = None
let mutable _currentKey  : Key option = None
let mutable _scriptChanged = false

let TextBoxScript = _window.FindName("TextBoxScript") :?> TextBox
let TextBoxOutput = _window.FindName("TextBoxOutput") :?> TextBox

let _openFileDialog = new System.Windows.Forms.OpenFileDialog()
_openFileDialog.Filter <- "F# script files (*.fsx)|*.fsx|All Files (*.*)|*.*"
_openFileDialog.FileOk.AddHandler(fun s e ->
                                    let mutable flag = true
                                    if _scriptChanged then
                                        let question = MessageBox.Show("There is unsaved work.\r\nOpen anyway?", "Unsaved Work", MessageBoxButton.YesNo, MessageBoxImage.Warning)
                                        if question = MessageBoxResult.No then
                                            e.Cancel <- true
                                            flag <- false

                                    if flag && System.IO.File.Exists(_openFileDialog.FileName) then
                                        use reader = new System.IO.StreamReader(_openFileDialog.FileName, true)
                                        TextBoxScript.Text <- reader.ReadToEnd()
                                        _scriptChanged <- false)

let _saveFileDialog = new System.Windows.Forms.SaveFileDialog()
_saveFileDialog.Filter <- "F# script files (*.fsx)|*.fsx|All Files (*.*)|*.*"

let SaveToSelectedFile() =
    if (System.String.IsNullOrWhiteSpace(_saveFileDialog.FileName) |> not) then
        use writer = new System.IO.StreamWriter(_saveFileDialog.FileName, false, Encoding.UTF8)
        writer.Write(TextBoxScript.Text)
        _scriptChanged <- false
    else
        ()

_saveFileDialog.FileOk.AddHandler(fun s e -> SaveToSelectedFile())

let SendSelectedCodeToFsi() =
    _fsi.StandardInput.Write(TextBoxScript.SelectedText + ";;\n\r\r")
    _fsi.StandardInput.Flush()

TextBoxScript.PreviewKeyDown.AddHandler(fun (sender : System.Object) (e : KeyEventArgs) ->
    _previousKey <- _currentKey
    _currentKey  <- Some e.SystemKey

    if e.Key = Key.Tab then
        e.Handled <- true
        let flag = TextBoxScript.SelectionStart
        TextBoxScript.Text <- TextBoxScript.Text.Insert(flag, _4spaces)
        TextBoxScript.SelectionStart <- flag + 4
    
    match _previousKey, _currentKey with
    | Some PK, Some CK -> 
        let combination = PK ||| CK

        let st1 = Key.LeftAlt ||| Key.Return
        let st2 = Key.RightAlt ||| Key.Return

        if combination = st1 || combination = st2 then

            _previousKey <- None
            _currentKey  <- None

            e.Handled <- true
            SendSelectedCodeToFsi()

        _previousKey <- None
    | _                -> ())

TextBoxScript.TextChanged.AddHandler(fun sender e ->
    _scriptChanged <- true)

TextBoxOutput.TextChanged.AddHandler(fun sender e ->
    TextBoxOutput.ScrollToEnd())

let ShutdownFsi() =
    if not (_fsi = null) then
        try
            _fsi.StandardInput.WriteLine("#quit;;")
            _fsi.Close()
        with
        | _ -> ()

        try
            if not (_fsi = null) then
                if (not _fsi.HasExited) && (not (_fsi.WaitForExit 1000)) then
                    _fsi.Kill();
        with
        | _ -> ()

let PrepareFsi() =
    ShutdownFsi();

    if not (System.IO.File.Exists(FsiPath)) then
        false
    else
        let fsiStart = new ProcessStartInfo(CreateNoWindow = true,
                                            UseShellExecute = false,
                                            FileName = FsiPath,
                                            RedirectStandardError = true,
                                            RedirectStandardInput = true,
                                            RedirectStandardOutput = true,
                                            ErrorDialog = false)
    
        _fsi <- new Process(StartInfo = fsiStart, EnableRaisingEvents = true)
        _fsi.Start() |> ignore

        let cleanUp str = System.Text.RegularExpressions.Regex.Replace(str, @"^([\s]*>[\s]*)+", t_str.Empty) + Environment.NewLine

        _fsi.ErrorDataReceived.AddHandler(fun s e ->
                                            Thread.Sleep(100)

                                            let act = Action (fun () -> TextBoxOutput.AppendText(e.Data + System.Environment.NewLine) |> ignore)
                                            
                                            _app.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Input, act) |> ignore)
    
        _fsi.OutputDataReceived.AddHandler(fun s e ->
                                            let act = Action (fun () -> TextBoxOutput.AppendText(cleanUp(e.Data)))
                                            _app.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Input, act) |> ignore)

        _fsi.BeginErrorReadLine()
        _fsi.BeginOutputReadLine()

        true

_window.Closing.AddHandler(fun s e ->
    let mutable flag = true

    if _scriptChanged then
        let question = MessageBox.Show("There is unsaved work.\r\nExit anyway?", "Unsaved Work", MessageBoxButton.YesNo, MessageBoxImage.Warning)

        if question = MessageBoxResult.No then
            e.Cancel <- true
            flag <- false

    if flag then
        _openFileDialog.Dispose()
        _saveFileDialog.Dispose()

        ShutdownFsi()
    ())

let MenuItem_Exit = _window.FindName("MenuItem_Exit") :?> MenuItem
MenuItem_Exit.Click.AddHandler(fun s e -> _window.Close())

let MenuItem_Open = _window.FindName("MenuItem_Open") :?> MenuItem
MenuItem_Open.Click.AddHandler(fun s e -> _openFileDialog.ShowDialog() |> ignore)

let MenuItem_Save = _window.FindName("MenuItem_Save") :?> MenuItem
MenuItem_Save.Click.AddHandler(fun s e ->
                                let cond1 = System.IO.File.Exists(_saveFileDialog.FileName)
                                let cond2 = System.IO.File.Exists(_openFileDialog.FileName)

                                if cond1 || cond2 then 
                                    if cond2 then
                                        _saveFileDialog.FileName <- _openFileDialog.FileName
                                    
                                    SaveToSelectedFile()
                                else
                                    _saveFileDialog.ShowDialog() |> ignore
                                    SaveToSelectedFile())

let MenuItem_SaveAs = _window.FindName("MenuItem_SaveAs") :?> MenuItem
MenuItem_SaveAs.Click.AddHandler(fun s e ->
                                    _saveFileDialog.ShowDialog() |> ignore
                                    SaveToSelectedFile())

let MenuItem_Copy = _window.FindName("MenuItem_Copy") :?> MenuItem
MenuItem_Copy.Click.AddHandler(fun s e -> TextBoxScript.Copy())

let MenuItem_Cut = _window.FindName("MenuItem_Cut") :?> MenuItem
MenuItem_Cut.Click.AddHandler(fun s e -> TextBoxScript.Cut())

let MenuItem_Paste = _window.FindName("MenuItem_Paste") :?> MenuItem
MenuItem_Paste.Click.AddHandler(fun s e -> TextBoxScript.Paste())

[<assembly: System.Reflection.AssemblyVersion("1.0.1.0")>]
[<assembly: System.Reflection.AssemblyFileVersion("1.0.1.0")>]
do()

TextBoxScript.Text <- "// select the code that you want to send to F# interactive then press alt+enter"
_scriptChanged <- false

[<EntryPoint>]
[<System.STAThreadAttribute>]
let main (args : string[]) =
    if not (PrepareFsi()) then
        MessageBox.Show(@"F# Interactive (fsi.exe) can't be found, check path in 'Funtastic.exe.config'", "Wrong Path", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore
    else
        _app.Run(_window) |> ignore

    0


